﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ES.Migrate
{
    /// <summary>
    /// 迁移工作者状态
    /// </summary>
    public enum WorkState
    {
        等待 = 0,
        执行中 = 1,
        暂停 = 2,
        继续 = 3,
        重做 = 4,
        取消 = 5,
        出错 = 6,
        校验文件 = 7,
        完成 = 8,
    }

    public class MigrateWorker
    {

        #region 属性
        private BackgroundWorker BgWork { get; set; }

        /// <summary>
        /// 任务状态
        /// </summary>
        private WorkState State { get; set; }

        /// <summary>
        /// 文件源路径
        /// </summary>
        public string SrcPath { get; private set; }

        /// <summary>
        /// 文件的目标路径
        /// </summary>
        public string TgtPath { get; private set; }

        /// <summary>
        /// 临时目录（存放断点数据文件）
        /// </summary>
        public string TempDir { get; private set; }

        /// <summary>
        /// 文件的目标目录
        /// </summary>
        public string TgtDir { get; private set; }


        /// <summary>
        /// 数据包大小(默认16mb)
        /// </summary>
        public long PackSize { get; set; } = 1024 * 1024 * 16;


        /// <summary>
        /// 文件大小
        /// </summary>
        public long FileLength { get; private set; }

        /// <summary>
        /// 需传输包的次数
        /// </summary>
        public int PackCount { get; private set; }

        /// <summary>
        /// 总进度：默认 PackCount*2
        /// 分片占100，合并分片占100,校验文件占1
        /// </summary>
        public int MaxProgress { get; set; }

        /// <summary>
        /// 当前进度
        /// </summary>
        public int CurrProgress;


        /// <summary>
        /// 进度百分比
        /// </summary>
        public double CurrRate
        {
            get
            {
                var re = Math.Round((this.CurrProgress * 1.00 / this.MaxProgress), 2);
                return re > 1 ? 1 : re;
            }
        }

        public string UserState
        {
            get;
            set;
        }
        #endregion


        #region 构造函数
        public MigrateWorker(string srcPath, string tgtPath, string userState)
            : this(srcPath, tgtPath, userState, 1024 * 1024 * 16)
        {

        }

        public MigrateWorker(string srcPath, string tgtPath, string userState, int packSize)
        {
            this.BgWork = new BackgroundWorker();
            this.BgWork.WorkerReportsProgress = true; // 设置可以通告进度
            this.BgWork.WorkerSupportsCancellation = true; // 
            this.BgWork.DoWork += MigrateWorker_DoWork;
            //this.BgWork.ProgressChanged += MigrateWorker_ProgressChanged;
            this.BgWork.RunWorkerCompleted += MigrateWorker_RunWorkerCompleted;
            this.State = WorkState.等待;

            this.SrcPath = srcPath;
            this.TgtPath = tgtPath;
            this.UserState = userState;
            this.PackSize = packSize;

            FileInfo fileInfo = new FileInfo(this.SrcPath);
            if (!fileInfo.Exists)
            {
                throw new ArgumentException("文件不存在！", "srcPath");
            }

            this.TgtDir = Path.GetDirectoryName(tgtPath);

            if (!Directory.Exists(this.TgtDir))
            {
                Directory.CreateDirectory(this.TgtDir);
            }

            this.FileLength = fileInfo.Length;

            if ((this.FileLength % this.PackSize) > 0)
            {
                this.PackCount = (int)(this.FileLength / this.PackSize) + 1;
            }
            else
            {
                this.PackCount = (int)(this.FileLength / this.PackSize);
            }

            this.MaxProgress = this.PackCount * 2 + 1;

            this.TempDir = Path.Combine(this.TgtDir, StrMD5(Path.GetFileName(this.TgtPath)));
        } 

        #endregion

        /// <summary>
        /// 进度百分比
        /// </summary>
        public Action<string,double> UpdateProgress { get; set; }

        public Action<string,Exception> WorkerError { get; set; }

        public Action<string, bool> WorkerCompleted { get; set; }

        /// <summary>
        /// 是否正在执行（忙碌）
        /// </summary>
        public bool IsBusy
        {
            get
            {
                return this.BgWork.IsBusy || this.State == WorkState.校验文件;
            }
        }

        /// <summary>
        /// 是否暂停
        /// </summary>
        private bool IsPause { set; get; }

        /// <summary>
        /// 等待不忙碌的睡眠时间
        /// </summary>
        private int SleepMS
        {
            get
            {
                return 500;
            }
        }

        /// <summary>
        /// 暂停
        /// </summary>
        public void PauseAsync()
        {
            this.IsPause = true;
            this.BgWork.CancelAsync();
        }

        /// <summary>
        /// 继续
        /// </summary>
        public void ContinceAsync()
        {
            while (true)
            {
                if (this.IsBusy)
                {
                    Thread.Sleep(SleepMS);
                }
                else
                {
                    break;
                }
            }
            this.IsPause = false;
            this.BgWork.RunWorkerAsync(WorkState.继续);
        }

        /// <summary>
        /// 重做
        /// </summary>
        public void RedoAsync()
        {
            while (true)
            {
                if (this.IsBusy)
                {
                    Thread.Sleep(SleepMS);
                }
                else
                {
                    break;
                }
            }
            this.CurrProgress = 0;
            this.IsPause = false;
            this.BgWork.RunWorkerAsync(WorkState.重做);
        }
  
        /// <summary>
        /// 取消
        /// </summary>
        public void CancelAsync()
        {
            if (this.BgWork.IsBusy) //暂停方式的取消
            {
                this.IsPause = false;
                this.BgWork.CancelAsync();
            }
            else
            {//真正的取消

                this.CurrProgress = 0;
                this.State = WorkState.取消;
                this.UpdateProgress?.Invoke(this.UserState, 0);
                DeleteOver(false);
            }
        }

        /// <summary>
        /// 执行
        /// </summary>
        public void RunWorkerAsync()
        {
            while (true)
            {
                if (this.IsBusy)
                {
                    Thread.Sleep(SleepMS);
                }
                else
                {
                    break;
                }
            }
            this.IsPause = false;
            this.BgWork.RunWorkerAsync(WorkState.执行中);
        }
        
        private void MigrateWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            this.State = (WorkState)Enum.Parse(typeof(WorkState), e.Argument.ToString());

            //重做的话，删除临时目录（下所有分片文件）
            if (this.State == WorkState.重做)
            {
                DeleteOver(false);
            }

            //临时文件夹路径
            CheckTempDir();
            var tempfiles = new DirectoryInfo(this.TempDir).GetFiles();
            List<string> Comparefiles = new List<string>();
            for (int j = 0; j < PackCount; j++)
            {
                bool hasfile = false;
                if (this.State != WorkState.重做)
                {
                    foreach (FileInfo Tempfile in tempfiles)
                    {
                        if (Tempfile.Name == GenerateTempName(j))
                        {
                            hasfile = true;
                            break;
                        }
                    }
                }
                if (hasfile == false)
                {
                    Comparefiles.Add(j.ToString());
                }
            }

            //最后补上这些缺失的文件                
            if (Comparefiles.Count > 0)
            {
                var tasks = new List<Task>();
                var fy = Task.Factory;
                foreach (string com_index in Comparefiles)
                {
                    string strIndex = com_index;
                    var task = fy.StartNew(() =>
                    {
                        //暂停 或 取消
                        if (this.BgWork.CancellationPending)
                        {
                            e.Cancel = true;
                            return;
                        }

                        string tempfilepath = Path.Combine(this.TempDir, GenerateTempName(strIndex));
                        using (FileStream Compstream = new FileStream(tempfilepath, FileMode.Create, FileAccess.Write, FileShare.Write))
                        {
                            int length = (int)Math.Min(PackSize, this.FileLength - Convert.ToInt32(strIndex) * this.PackSize);
                            var bytes = GetFile(Convert.ToInt64(strIndex) * PackSize, length);
                            Compstream.Write(bytes, 0, length);
                            Compstream.Flush();
                            Compstream.Close();
                            Compstream.Dispose();
                        }

                        //报告进度
                        Interlocked.Increment(ref this.CurrProgress);
                        this.UpdateProgress?.Invoke(this.UserState, this.CurrRate);

                    });
                    tasks.Add(task);
                }
                //等待所有线程完成
                Task.WaitAll(tasks.ToArray());
            }
           
            if (!e.Cancel)
            {
                this.CurrProgress = tempfiles.Length;
                this.UpdateProgress?.Invoke(this.UserState, this.CurrRate);

                //throw new Exception("测试出错！！！！！！！！");

                var tempDirInfo = new DirectoryInfo(this.TempDir);
                using (FileStream writestream = new FileStream(this.TgtPath, FileMode.Create, FileAccess.Write, FileShare.Write))
                {
                    tempfiles = tempDirInfo.GetFiles();
                    foreach (FileInfo fileInfo in tempfiles)
                    {
                        //暂停列表中 包含该 任务Id
                        if (this.BgWork.CancellationPending)
                        {
                            e.Cancel = true;
                            break;
                        }

                        using (FileStream readTempStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                        {
                            long onefileLength = fileInfo.Length;
                            byte[] buffer = new byte[Convert.ToInt32(onefileLength)];
                            readTempStream.Read(buffer, 0, Convert.ToInt32(onefileLength));
                            writestream.Write(buffer, 0, Convert.ToInt32(onefileLength));
                        }

                        //报告进度
                        Interlocked.Increment(ref this.CurrProgress);
                        this.UpdateProgress?.Invoke(this.UserState, this.CurrRate);
                    }
                    writestream.Flush();
                    writestream.Close();
                    writestream.Dispose();
                }
            }
        }
        
        private void MigrateWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                this.State = WorkState.出错;
                this.WorkerError?.Invoke(this.UserState, e.Error);
            }
            else if (e.Cancelled)
            {
                if (this.IsPause)
                {
                    this.State = WorkState.暂停;
                    this.CurrProgress = new DirectoryInfo(this.TempDir).GetFiles().Length;
                }
                else //真正的取消
                {
                    
                }
            }
            else
            {
                this.State = WorkState.校验文件;
                bool isOK = Md5Compare();
                if (!isOK)
                {
                    this.BgWork.RunWorkerAsync(WorkState.重做);
                }
                this.State = WorkState.完成;
                this.UpdateProgress?.Invoke(this.UserState, 1);
                DeleteOver(true);
                this.WorkerCompleted?.Invoke(this.UserState, isOK);
            }
        }

        #region private 方法

        /// <summary>
        /// 根据开始位置获取文件字节流
        /// </summary>
        /// <param name="start"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        private byte[] GetFile(long start, int length)
        {
            using (FileStream ServerStream = new FileStream(this.SrcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1024 * 80, true))
            {
                byte[] buffer = new byte[length];
                ServerStream.Position = start;
                //ServerStream.Seek(start, SeekOrigin.Begin);
                ServerStream.Read(buffer, 0, length);
                return buffer;
            }
        }

        /// <summary>
        /// 检测临时目录是否存在，不存在则创建
        /// </summary>
        private void CheckTempDir()
        {
            if (!Directory.Exists(this.TempDir))
            {
                Directory.CreateDirectory(this.TempDir);
            }
        }

        /// <summary>
        /// 删除已经完成的
        /// </summary>
        private void DeleteOver(bool isSuccess)
        {
            if (Directory.Exists(this.TempDir))
            {
                Directory.Delete(this.TempDir, true);
            }

            if (!isSuccess && File.Exists(this.TgtPath))
            {
                File.Delete(this.TgtPath);
            }
        }


        /// <summary>
        /// 生成临时文件名称
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        private string GenerateTempName(object index)
        {
            string res = index.ToString().PadLeft(this.PackCount.ToString().Length, '0') + "_" + this.PackCount;
            return res;
        }


        /// <summary>
        /// md5比对文件
        /// </summary>
        /// <returns></returns>
        private bool Md5Compare()
        {
            string md51 = FileMD5(this.SrcPath);
            string md52 = FileMD5(this.TgtPath);

            if (md51 == null || md52 == null)
            {
                return false;
            }
            return md51.Equals(md52);
        }


        /// <summary>
        /// 计算文件的Md5
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private static string FileMD5(string path)
        {
            if (!File.Exists(path))
            {
                return null;
            }
            int bufferSize = 1024 * 16;
            byte[] buffer = new byte[bufferSize];
            Stream inputStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
            HashAlgorithm hashAlgorithm = new MD5CryptoServiceProvider();
            int readLength = 0;//每次读取长度
            var output = new byte[bufferSize];
            while ((readLength = inputStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                //计算MD5
                hashAlgorithm.TransformBlock(buffer, 0, readLength, output, 0);
            }
            //完成最后计算，必须调用(由于上一部循环已经完成所有运算，所以调用此方法时后面的两个参数都为0)
            hashAlgorithm.TransformFinalBlock(buffer, 0, 0);
            string md5 = BitConverter.ToString(hashAlgorithm.Hash).Replace("-", "");
            hashAlgorithm.Clear();
            inputStream.Close();
            inputStream.Dispose();
            return md5;
        }

        /// <summary>
        /// 字符串Md5
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        private static string StrMD5(string source)
        {
            byte[] sor = Encoding.UTF8.GetBytes(source);
            MD5 md5 = MD5.Create();
            byte[] result = md5.ComputeHash(sor);
            StringBuilder strbul = new StringBuilder(40);
            for (int i = 0; i < result.Length; i++)
            {
                strbul.Append(result[i].ToString("x2"));//加密结果"x2"结果为32位,"x3"结果为48位,"x4"结果为64位

            }
            return strbul.ToString();
        }

        #endregion
    }
}
